﻿//
// Copyright 2024 Exhunter Technology Co.Ltd2.
//

using System.Threading;
using System;

namespace X18.Framework.Core.Sequential
{
    /// <summary>
    /// 雪花ID（Snowflake ID）
    /// </summary>
    public class SnowflakeId
    {
        /// <summary>
        /// 起始时间戳(假设为2013-01-01 00:00:00的毫秒数)
        /// </summary>
        private const long Epoch = 687834000000L;
        /// <summary>
        /// 机器标识所占的位数
        /// </summary>
        private const long WorkerIdBits = 5;
        /// <summary>
        /// 数据中心标识所占的位数
        /// </summary>
        private const long DataCenterIdBits = 5;
        /// <summary>
        /// 支持的最大机器标识数量
        /// </summary>
        private const long MaxWorkerId = -1L ^ (-1L << (int)WorkerIdBits);
        /// <summary>
        /// 支持的最大数据中心标识数量
        /// </summary>
        private const long MaxDatacenterId = -1L ^ (-1L << (int)DataCenterIdBits);
        /// <summary>
        /// 序列号所占的位数
        /// </summary>
        private const long SequenceBits = 12L;
        /// <summary>
        /// 机器标识左移位数
        /// </summary>
        private const long WorkerIdShift = SequenceBits;
        /// <summary>
        /// 数据中心标识左移位数
        /// </summary>
        private const long DataCenterIdShift = SequenceBits + WorkerIdBits;
        /// <summary>
        /// 时间戳左移位数
        /// </summary>
        private const long TimestampLeftShift = SequenceBits + WorkerIdBits + DataCenterIdBits;
        /// <summary>
        /// 序列号掩码
        /// </summary>
        private const long SequenceMask = -1L ^ (-1L << (int)SequenceBits);

        /// <summary>
        /// 序列同步锁
        /// </summary>
        private static readonly object SyncLock = new object();
        /// <summary>
        /// 机器标识
        /// </summary>
        private static long WorkerId;
        /// <summary>
        /// 数据中心标识
        /// </summary>
        private static long DataCenterId;
        /// <summary>
        /// 序列号
        /// </summary>
        private static long Sequence = 0L;
        /// <summary>
        /// 上次生成ID的时间戳
        /// </summary>
        private static long LastTimestamp = -1L;

        public static void InitWorkerIdDataCenterId(long WorkerId, long DataCenterId)
        {
            lock (SyncLock)
            {
                InitWorkerIdDataCenterId0(WorkerId, DataCenterId);
            }
        }

        private static void InitWorkerIdDataCenterId0(long WorkerId, long DataCenterId)
        {
            if (WorkerId > MaxWorkerId || WorkerId < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(WorkerId), WorkerId, "Worker ID Out Of Range");
            }

            if (DataCenterId > MaxDatacenterId || DataCenterId < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(DataCenterId), DataCenterId, "Datacenter ID  Out Of Range");
            }

            SnowflakeId.WorkerId = WorkerId;
            SnowflakeId.DataCenterId = DataCenterId;
        }

        public static long NextId()
        {
            lock (SyncLock)
            {
                return NextId0();
            }
        }

        private static long NextId0()
        {
            long timestamp = TimeGen();

            if (timestamp < LastTimestamp)
            {
                TilNextMillis(LastTimestamp);
            }

            if (LastTimestamp == timestamp)
            {
                Sequence = (Sequence + 1) & SequenceMask;
                if (Sequence == 0)
                {
                    timestamp = TilNextMillis(LastTimestamp);
                }
            }
            else
            {
                Sequence = 0L;
            }

            LastTimestamp = timestamp;

            return ((timestamp - Epoch) << (int)TimestampLeftShift) |
                   (DataCenterId << (int)DataCenterIdShift) |
                   (WorkerId << (int)WorkerIdShift) |
                   Sequence;
        }

        protected static long TilNextMillis(long lastTimestamp)
        {
            long timestamp = TimeGen();
            while (timestamp <= lastTimestamp)
            {
                timestamp = TimeGen();
                Thread.Sleep(1);
            }
            return timestamp;
        }

        protected static long TimeGen()
        {
            return DateTimeOffset.Now.Millisecond;
        }
    }
}
